using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.CodeDom.Compiler;
using System.IO;

using NBusiness.CodeDom;
using NBusiness.ESharp;
using NBusiness.CodeDom.Compiler;
using NBusiness.ESharp.Compiler;

namespace esc
{
	internal class Program
	{
		#region Expressions
		private static class Expressions
		{
			public static Regex Output = new Regex(@"^[/](out|o)$");
			public static Regex OutputArg = new Regex(@"^.*([.]dll)$");
			public static Regex Debug = new Regex(@"^[/](debug|d)$");
			public static Regex TrueFalse = new Regex(@"^(true|false)$");
			public static Regex CodeFile = new Regex(@"^.+[.].+$");
			public static Regex Reference = new Regex(@"^[/](r|reference)$");
			public static Regex ReferenceArg = new Regex(@"^(.*[.]dll)$");
			public static Regex Silent = new Regex(@"^[/](s|silent)$");
			public static Regex Path = new Regex(@"^[/](p|path)$");
            public static Regex Embed = new Regex(@"^[/](e|embed)$");
		} 
		#endregion

		#region Main
		static void Main(string[] args)
		{
			Program p = new Program(args);
		} 
		#endregion

		#region Constructor
		private Program(string[] args)
		{
			Argument[] Arguments = CreateArguments(args);
			if (AssignArgumentTypes(Arguments))
			{
                EntityCompilerParameters parameters = new EntityCompilerParameters();
				LoadCompilerParameters(parameters, Arguments);

                EntityResourceFile[] Files = GetCodeFiles(Arguments);

				string OutputPath = GetOutputPath(Arguments);
				bool Silent = GetSilent(Arguments);

				if (OutputPath == null ||
					Files.Length == 0)
				{
					PrintUsage();
				}
				else
				{
					Compile(parameters, OutputPath, Files);
				}
			}
			else PrintUsage();
		}
		#endregion

		#region Compile
        private void Compile(EntityCompilerParameters parameters, string OutputPath, EntityResourceFile[] Files)
		{
			Compile(parameters, OutputPath, Files, false);
		}
        private void Compile(EntityCompilerParameters parameters, string OutputPath, EntityResourceFile[] Files, bool Silent)
		{
			ESharpProvider esp = new ESharpProvider();
			CompilerResults results = esp.CompileAssemblyFromFiles(parameters, Files);

			if (!Silent)
			{
                bool success = true;
				if (results.Errors.Count > 0)
				{
					foreach (CompilerError error in results.Errors)
					{
                        if (!error.IsWarning)
                        {
                            success = false;
                            Console.ForegroundColor = ConsoleColor.Red;
                        }
                        else Console.ForegroundColor = ConsoleColor.Yellow;

                        Console.WriteLine(string.Format(
                            "{0}: {1}",
                            (error.IsWarning ? "Warning" : "Error"),
                            error.ErrorText));

                        if (!string.IsNullOrEmpty(error.FileName))
                        {
                            Console.WriteLine(string.Format(
                                "\tin '{0}' Line: {1} Column: {2}",
                                Path.GetFileName(error.FileName),
                                error.Line,
                                error.Column));
                        }

                        Console.WriteLine();
					}
				}

                Console.ForegroundColor = ConsoleColor.White;
                if(success) Console.WriteLine("Compilation successful...");
                else Console.WriteLine("Compilation failed...");
			}
		} 
		#endregion

		#region Print Usage
		public void PrintUsage()
		{
			Console.WriteLine(
				"Invalid Syntax Error...\r\n\r\n" +
				"Usage Syntax:\r\n" +
				"esc /out path.dll (/[silent|s] [true|false])? (/[debug|d] [true|false])? (/[reference|r] path.dll)* " +
				"(/[path|p] [.])? (/[embed|e] [*.*])" +
				"(*.*)+\r\n\r\n" +
				"Example Usage:\r\n" +
				"esc /o path.dll /s true file1.es file2.cs\r\n" +
				"esc /out path.dll /debug true file1.es file2.cs\r\n" +
				"esc /o path.dll /r Ref.dll file1.es file2.cs file3.es\r\n" +
				"esc /o path.dll /r Ref.dll /e img.png file1.es file2.cs file3.es\r\n" +
				"esc /o path.dll file1.es file2.cs\r\n" +
				"esc /out path.dll file.es\r\n\r\n" +
				"Command\t\tArgument\tDescription  \r\n" +
				"out\t\tpath\t\tThe path to the built assembly\r\n" +
				"debug\t\ttrue|false\tOptionally specifies to generate debug info\r\n" +
				"reference\t*.dll\t\tAdd extra assemblies as references\r\n" +
				"silent\t\ttrue|false\tSupresses console output during compilation\r\n" +
				"path\t\t*\t\tAdds paths for finding templates\r\n" +
                "embed\t\t*\t\tEmbeds file in output assembly\r\n" +
				"code files\t*.es|*.cs\tFiles to compile\r\n");
		} 
		#endregion

		#region Create Arguments
		private Argument[] CreateArguments(string[] args)
		{
			List<Argument> Arguments = new List<Argument>();
			foreach (string arg in args)
			{
				Arguments.Add(new Argument(arg));
			}
			return Arguments.ToArray();
		} 
		#endregion

		#region Assign Argument Types
		private bool AssignArgumentTypes(Argument[] Arguments)
		{
			for (int x = 0; x < Arguments.Length; x++)
			{
				if (x == 0 && Expressions.Output.IsMatch(Arguments[x].Value))
				{
					Arguments[x].Type = ArgumentType.Output;
				}
				else if (x > 0)
				{
					//Switch on the last argument type
					switch (Arguments[x - 1].Type)
					{
						#region Case Output
						case (ArgumentType.Output):
							{
								if (Expressions.OutputArg.IsMatch(Arguments[x].Value))
								{
									Arguments[x].Type = ArgumentType.OutputArg;
								}
								break;
							}
						#endregion
						#region Case OutputArg | PathArg | SilentArg | DebugArg | EmbedArg
						case ArgumentType.PathArg:
						case ArgumentType.SilentArg:
						case ArgumentType.DebugArg:
                        case ArgumentType.EmbedArg:
						case ArgumentType.OutputArg:
							{
								if (Expressions.Debug.IsMatch(Arguments[x].Value))
								{
									Arguments[x].Type = ArgumentType.Debug;
								}
								else if (Expressions.Silent.IsMatch(Arguments[x].Value))
								{
									Arguments[x].Type = ArgumentType.Silent;
								}
								else if (Expressions.Reference.IsMatch(Arguments[x].Value))
								{
									Arguments[x].Type = ArgumentType.Reference;
								}
								else if (Expressions.CodeFile.IsMatch(Arguments[x].Value))
								{
									Arguments[x].Type = ArgumentType.Codefile;
								}
								else if (Expressions.Path.IsMatch(Arguments[x].Value))
								{
									Arguments[x].Type = ArgumentType.Path;
								}
                                else if (Expressions.Embed.IsMatch(Arguments[x].Value))
                                {
                                    Arguments[x].Type = ArgumentType.Embed;
                                }
								break;
							}
						#endregion
						#region Case Reference
						case ArgumentType.Reference:
							{
								if (Expressions.ReferenceArg.IsMatch(Arguments[x].Value))
								{
									Arguments[x].Type = ArgumentType.ReferenceArg;
								}
								break;
							}
						#endregion
						#region Case Path
						case ArgumentType.Path:
							{
								if (Path.GetFullPath(Arguments[x].Value) != null)
								{
									Arguments[x].Type = ArgumentType.PathArg;
								}
								break;
							}
						#endregion
						#region Case Debug
						case (ArgumentType.Debug):
							{
								if (Expressions.TrueFalse.IsMatch(Arguments[x].Value.ToLower()))
								{
									Arguments[x].Type = ArgumentType.DebugArg;
								}
								break;
							}
						#endregion
						#region Case Silent
						case ArgumentType.Silent:
							{
								if (Expressions.TrueFalse.IsMatch(Arguments[x].Value))
								{
									Arguments[x].Type = ArgumentType.SilentArg;
								}
								break;
							}
						#endregion
                        #region Case Embed
                        case ArgumentType.Embed:
                            {
                                if (Expressions.Path.Equals(Arguments[x].Value))
                                {
                                    Arguments[x].Type = ArgumentType.EmbedArg;
                                }
                                break;
                            } 
                        #endregion
						#region Case Codefile
						case ArgumentType.Codefile:
							{
								if (Expressions.CodeFile.IsMatch(Arguments[x].Value))
								{
									Arguments[x].Type = ArgumentType.Codefile;
								}
								break;
							}
						#endregion
					}
					if (Arguments[x].Type == ArgumentType.Unknown) return false;
				}
			}
			return true;
		}
		#endregion

		#region Load Compiler Parameters
        private void LoadCompilerParameters(EntityCompilerParameters parameters, Argument[] Arguments)
		{
			parameters.GenerateInMemory = false;
			parameters.IncludeDebugInformation = false;

			foreach (Argument a in Arguments)
			{
				switch (a.Type)
				{
					#region Case Debug Argument
					case ArgumentType.DebugArg:
						{
							parameters.IncludeDebugInformation = bool.Parse(a.Value);
							break;
						} 
					#endregion
					#region Case OutputArg
					case ArgumentType.OutputArg:
						{
							parameters.OutputAssembly = Path.GetFullPath(a.Value);
							break;
						} 
					#endregion
					#region Case ReferenceArg
					case ArgumentType.ReferenceArg:
						{
							parameters.ReferencedAssemblies.Add(a.Value);
							break;
						} 
					#endregion
				}
			}
            if (parameters.ReferencedAssemblies.Count == 0)
			{
                parameters.ReferencedAssemblies.Add("System.dll");
                parameters.ReferencedAssemblies.Add("System.Data.dll");
                parameters.ReferencedAssemblies.Add("NBusiness.dll");
			}
		}
		#endregion

		#region Get Code Files
        private EntityResourceFile[] GetCodeFiles(Argument[] Arguments)
		{
            List<EntityResourceFile> Files = new List<EntityResourceFile>();
			foreach (Argument a in Arguments)
			{
				if (a.Type == ArgumentType.Codefile)
				{
                    EntityResourceFile r = new EntityResourceFile();
                    r.FileName = Path.GetFullPath(a.Value);
                    r.Action = EntityBuildAction.Compile;
					Files.Add(r);
				}
                else if (a.Type == ArgumentType.EmbedArg)
                {
                    EntityResourceFile r = new EntityResourceFile();
                    r.FileName = Path.GetFullPath(a.Value);
                    r.Action = EntityBuildAction.EmbeddedResource;
                    Files.Add(r);
                }
			}
			return Files.ToArray();
		}
		#endregion

		#region Get Output Path
		private string GetOutputPath(Argument[] Arguments)
		{
			foreach (Argument a in Arguments)
			{
				if (a.Type == ArgumentType.OutputArg)
				{
					return a.Value;
				}
			}

			return null;
		} 
		#endregion

		#region Get Silent
		private bool GetSilent(Argument[] Arguments)
		{
			foreach (Argument a in Arguments)
			{
				if (a.Type == ArgumentType.SilentArg)
				{
					return bool.Parse(a.Value);
				}
			}

			return false;
		}  
		#endregion
	}
}
